import Foundation
import CoreData
import LXExtensions

public typealias XXManagedObjectContext = NSManagedObjectContext

@available(iOS 10.0, *)
public class XXCoreData: NSObject {
    public let persistentContainer: NSPersistentContainer
    public let generalViewContext: XXManagedObjectContext

    public init(persistent: NSPersistentContainer) {
        persistentContainer = persistent
        generalViewContext = persistent.viewContext
        generalViewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    }

    //MARK: -NSManagedObject 相关
      
    /// 创建一个实例对象,不写入上下文
    public func create<T: NSManagedObject>() -> T {
        let name = "\(T.self)"
        let entity = NSEntityDescription.entity(forEntityName: name, in: generalViewContext)!
        let model = T.init(entity: entity, insertInto: nil)
        return model
    }
    
    
    /// 把创建的实例对象写入数据库上下文,这样保存上下文时就能保存该实例
    /// - Parameter item: 对象
    public func insetToContext(item: NSManagedObject, context: XXManagedObjectContext? = nil) {
        let context = context ?? generalViewContext
        if item.managedObjectContext == nil {
            context.insert(item)
        }
    }
    
    /// 创建一个实例对象,写入上下文,保存时会直接保存到数据库
    public func createInContext<T: NSManagedObject>(_ context: XXManagedObjectContext? = nil) -> T {
        let context = context ?? generalViewContext
        let name = "\(T.self)"
        let model = NSEntityDescription.insertNewObject(forEntityName: name, into: context) as! T
        return model
    }


    //MARK: -NSManagedObjectContext相关
    
    /// 创建一个新的上下文
    public func createMainContext() -> XXManagedObjectContext {
        let context = persistentContainer.newBackgroundContext()
        context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        return context
    }

    /// 保存上下文
    public func saveContext(_ context: XXManagedObjectContext? = nil) throws {
        let context = context ?? generalViewContext
        try context.save()
        // 如果当前上下文不是主上下文,那么如果主上下文和当前上下文同时持有一个对象,会导致保存时冲突;
        // 给上下文设置好合并策略,然后保存其它上下文时刷新一下主上下文
        if context != generalViewContext {
            generalViewContext.refreshAllObjects()
        }
    }
    
    public func resetContext(_ context: XXManagedObjectContext? = nil) {
        let context = context ?? generalViewContext
        context.reset()
    }
    
    public func undoContext(_ context: XXManagedObjectContext? = nil) {
        let context = context ?? generalViewContext
        context.undo()
    }
    
    public func deleteFromDB(_ item: NSManagedObject, context: XXManagedObjectContext? = nil) {
        let context = context ?? generalViewContext
        context.delete(item)
    }
    
    public func deleteFromContext(_ item: NSManagedObject, context: XXManagedObjectContext? = nil) {
        let context = context ?? generalViewContext
        context.refresh(item, mergeChanges: false)
    }
    
    public func refresh(_ item: NSManagedObject, context: XXManagedObjectContext? = nil, mergeChanges: Bool = false) {
        let context = context ?? generalViewContext
        context.refresh(item, mergeChanges: mergeChanges)
    }

    
    //MARK: -数据库操作相关
    
    /// 查询
    public func query<T:NSManagedObject>(_ predicateString: String = "", sorts:[NSSortDescriptor]?  = nil, context: XXManagedObjectContext? = nil) throws -> [T] {
        let context = context ?? generalViewContext
        let fetchRequest:NSFetchRequest<T> = T.fetchRequest() as? NSFetchRequest<T> ?? NSFetchRequest<T>.init(entityName: "\(T.self)")
        if !predicateString.isEmpty {
            fetchRequest.predicate = NSPredicate.init(format: predicateString,"")
        }
        fetchRequest.sortDescriptors = sorts
        let results = try context.fetch(fetchRequest)
        return results
    }
    
    /// 分页查询
    public func query<T:NSManagedObject>(_ predicateString: String = "", sorts:[NSSortDescriptor]?  = nil, context: XXManagedObjectContext? = nil, pageNum:Int, pageSize: Int) throws -> [T] {
        let context = context ?? generalViewContext
        let fetchRequest:NSFetchRequest<T> = T.fetchRequest() as? NSFetchRequest<T> ?? NSFetchRequest<T>.init(entityName: "\(T.self)")
        fetchRequest.fetchLimit = pageSize
        fetchRequest.fetchOffset = pageNum * pageSize
        if !predicateString.isEmpty {
            fetchRequest.predicate = NSPredicate.init(format: predicateString)
        }
        fetchRequest.sortDescriptors = sorts
        let results = try context.fetch(fetchRequest)
        return results
    }
    
    public func queryColumnData<T:NSManagedObject>(entity: T.Type, propertiy:String, isDistinct: Bool = true, predicateString: String = "", sorts:[NSSortDescriptor]?  = nil, context: XXManagedObjectContext? = nil) throws -> [Any] {
        let context = context ?? generalViewContext
        let fetchRequest = NSFetchRequest<NSDictionary>(entityName: "\(entity.self)")
        
        if !predicateString.isEmpty {
            fetchRequest.predicate = NSPredicate.init(format: predicateString,"")
        }
        fetchRequest.sortDescriptors = sorts
        fetchRequest.propertiesToFetch = [propertiy]
        fetchRequest.resultType = .dictionaryResultType
        fetchRequest.returnsDistinctResults = isDistinct
        let results = try context.fetch(fetchRequest)
        LXLog(results)
        let finalArr = results.map({$0[propertiy]!})
        return finalArr
    }
    
    public func queryColumnDataList<T:NSManagedObject>(entity: T.Type, properties:[String], isDistinct: Bool = true, predicateString: String = "", sorts:[NSSortDescriptor]?  = nil, context: XXManagedObjectContext? = nil) throws -> [NSDictionary] {
        let context = context ?? generalViewContext
        let fetchRequest = NSFetchRequest<NSDictionary>(entityName: "\(entity.self)")
        
        if !predicateString.isEmpty {
            fetchRequest.predicate = NSPredicate.init(format: predicateString,"")
        }
        fetchRequest.sortDescriptors = sorts
        fetchRequest.propertiesToFetch = properties
        fetchRequest.resultType = .dictionaryResultType
        fetchRequest.returnsDistinctResults = isDistinct
        let results = try context.fetch(fetchRequest)
        LXLog(results)
        return results
    }

    
    @discardableResult
    /// 删除
    public func delete<T:NSManagedObject>(_ predicateString: String = "", context: XXManagedObjectContext? = nil) throws -> [T] {
        let context = context ?? generalViewContext
        let results: [T] = try query(predicateString, context: context)
        results.forEach { item in
            deleteFromDB(item, context:context)
        }
        try saveContext(context)
        return results
    }
    
    @discardableResult
    /// 修改
    public func modify<T:NSManagedObject>(_ predicateString: String = "", closure: @escaping ([T]) -> Void, context: XXManagedObjectContext? = nil) throws -> [T] {
        let context = context ?? generalViewContext
        let results: [T] = try query(predicateString, context: context)
        closure(results)
        try saveContext(context)
        return results
    }
    
    @discardableResult
    /// 添加
    public func add<T:NSManagedObject>(closure: @escaping (inout T) -> Void, context: XXManagedObjectContext? = nil) throws -> T {
        let context = context ?? generalViewContext
        var model: T = createInContext(context)
        closure(&model)
        try saveContext(context)
        return model
    }
    
    //MARK: - Batch
     
     @discardableResult
     /// 删除
     public func batchDelete<T:NSManagedObject>(_ entity: T.Type, predicateString: String = "") throws -> [NSManagedObjectID] {
         
         let fetchRequest:NSFetchRequest<T> = T.fetchRequest() as? NSFetchRequest<T> ?? NSFetchRequest<T>.init(entityName: "\(T.self)")
         if !predicateString.isEmpty {
             fetchRequest.predicate = NSPredicate.init(format: predicateString,"")
         }

         let batchDeleteRequest = NSBatchDeleteRequest.init(fetchRequest: fetchRequest as! NSFetchRequest<NSFetchRequestResult>)
         batchDeleteRequest.resultType = .resultTypeObjectIDs
         let result = try persistentContainer.viewContext.execute(batchDeleteRequest) as! NSBatchDeleteResult
         let changedIds = result.result as? [NSManagedObjectID] ?? []
         let changes: [AnyHashable: Any] = [
             NSDeletedObjectsKey: changedIds
         ]
         NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [persistentContainer.viewContext])
         try saveContext()
         return changedIds
     }
     
     @discardableResult
     /// 插入
     public func batchInsert<T:NSManagedObject>(_ entity: T.Type, objects: [[String: Any]]) throws -> [NSManagedObjectID] {
         let insert = NSBatchInsertRequest.init(entityName: "\(T.self)", objects: objects)
         insert.resultType = .objectIDs
         persistentContainer.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
         
         let result = try persistentContainer.viewContext.execute(insert) as! NSBatchInsertResult
         return result.result as? [NSManagedObjectID] ?? []
     }
     
     @discardableResult
     /// 批量更新
     public func batchUpdate<T:NSManagedObject>(_ entity: T.Type, propertiesToUpdate: [AnyHashable : Any], predicateString: String = "") throws -> [NSManagedObjectID] {

         let update = NSBatchUpdateRequest.init(entityName: "\(T.self)")
         if !predicateString.isEmpty {
             update.predicate = NSPredicate.init(format: predicateString,"")
         }
         update.resultType = .updatedObjectIDsResultType
         update.propertiesToUpdate = propertiesToUpdate
         
         let result = try persistentContainer.viewContext.execute(update) as! NSBatchUpdateResult
         let changedIds = result.result as? [NSManagedObjectID] ?? []
         let changes: [AnyHashable: Any] = [
             NSUpdatedObjectsKey: changedIds
         ]
         NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [persistentContainer.viewContext])
         try saveContext()
         return changedIds
     }
}
